package cn.dengta.webapp.user.module.file;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;

import me.codeplayer.util.EasyDate;

/**
 * 文件名称构造器，用于构造符合指定模式的文件名称
 */
public class FileNameBuilder {

	protected static Pattern REGEX = Pattern.compile("yyyy|MM|dd|HH|mm|ss|SSS|fileName|fileExt|time|rand:\\d+|<[^>]+>");
	protected final String pattern;
	protected PartParserEntry[] parserEntries;
	protected int capacity = -1;

	public FileNameBuilder(String pattern) {
		this.pattern = pattern;
	}

	/**
	 * 根据指定的原始文件名称和日期基于当前模式构造新的文件名称
	 *
	 * @param fileName 原始文件名称
	 * @param date 指定的日期，如果为null，则默认为当前时间
	 * @param context 命名上下文对象，用于配合属性占位符使用，可以为null
	 * @author Ready
	 * @since 1.0
	 */
	public String build(String fileName, @Nullable Date date, Map<String, String> context) {
		initParser();
		if (parserEntries.length == 0) {
			return pattern;
		}
		int length = pattern.length();
		StringBuilder sb = new StringBuilder(capacity == -1 ? length + 6 : capacity + 1);
		EasyDate easyDate = date == null ? new EasyDate() : new EasyDate(date);
		for (int i = 0, cursor = 0; i < length; cursor++) {
			if (cursor < parserEntries.length) {
				PartParserEntry entry = parserEntries[cursor];
				if (i < entry.begin) {
					sb.append(pattern, i, i = entry.begin);
				}
				if (i == entry.begin) {
					entry.parser.parse(sb, fileName, easyDate, context);
					i = entry.end;
				}
			} else {
				sb.append(pattern, i, i = length);
			}
		}
		int pos = fileName.lastIndexOf('.');
		if (pos != -1) {
			sb.append(fileName, pos, fileName.length());
		}
		if (capacity == -1) {
			capacity = sb.length();
		}
		return sb.toString();
	}

	protected void initParser() {
		if (parserEntries == null) {
			synchronized (this) {
				if (parserEntries == null) {
					List<PartParserEntry> entries = new ArrayList<>();
					Matcher matcher = REGEX.matcher(pattern);
					while (matcher.find()) {
						String group = matcher.group(0);
						PartParser parser = cachedParsers.get(group);
						if (parser == null) {
							if (group.charAt(0) == '<') {
								// "${.+}"
								parser = new ContextPartParser(group.substring(1, group.length() - 1));
							} else if (group.startsWith("rand:")) {
								// "rand:\d+"
								parser = new RandomPartParser(Integer.parseInt(group.substring(5)));
							}
						}
						if (parser != null) {
							entries.add(new PartParserEntry(matcher.start(), matcher.end(), parser));
						}
					}
					parserEntries = entries.toArray(new PartParserEntry[0]);
				}
			}
		}
	}

	public interface PartParser {

		void parse(StringBuilder sb, String fileName, EasyDate date, Map<String, String> context);

	}

	private static final Map<String, PartParser> cachedParsers = new HashMap<>() {

		private static final long serialVersionUID = 1L;

		{
			this.put("yyyy", (sb, fileName, date, context) -> sb.append(date.getYear()));
			this.put("MM", (sb, fileName, date, context) -> zeroFillUpTo2(sb, date.getMonth()));
			this.put("dd", (sb, fileName, date, context) -> zeroFillUpTo2(sb, date.getDay()));
			this.put("HH", (sb, fileName, date, context) -> zeroFillUpTo2(sb, date.getHour()));
			this.put("mm", (sb, fileName, date, context) -> zeroFillUpTo2(sb, date.getMinute()));
			this.put("ss", (sb, fileName, date, context) -> zeroFillUpTo2(sb, date.getSecond()));
			this.put("SSS", (sb, fileName, date, context) -> zeroFillUpTo2(sb, date.getMillisecond()));
			this.put("time", (sb, fileName, date, context) -> sb.append(date.getTime()));
			this.put("fileName", (sb, fileName, date, context) -> {
				int pos = fileName.lastIndexOf('.');
				if (pos >= 0) {
					sb.append(fileName, 0, pos);
				} else {
					sb.append(fileName);
				}
			});
			this.put("fileExt", (sb, fileName, date, context) -> {
				int pos = fileName.lastIndexOf('.');
				if (pos >= 0) {
					sb.append(fileName, pos + 1, fileName.length());
				}
			});
		}
	};

	public static class RandomPartParser implements PartParser {

		protected final int length;

		public RandomPartParser(int length) {
			this.length = length;
		}

		public void parse(StringBuilder sb, String fileName, EasyDate date, Map<String, String> context) {
			int remain = length;
			while (remain > 0) {
				String str = Double.toString(Math.random()).substring(2);
				int strLength = str.length();
				sb.append(str, 0, Math.min(remain, strLength));
				remain -= strLength;
			}
		}

	}

	public static class ContextPartParser implements PartParser {

		protected final String key;

		public ContextPartParser(String contextKey) {
			this.key = contextKey;
		}

		public void parse(StringBuilder sb, String fileName, EasyDate date, Map<String, String> context) {
			if (context != null) {
				sb.append(context.get(key));
			}
		}

	}

	public static void zeroFillUpTo2(StringBuilder sb, int val) {
		if (val < 10) {
			sb.append('0');
		}
		sb.append(val);
	}

	public static class PartParserEntry {

		protected int begin;
		protected int end;
		protected PartParser parser;

		public PartParserEntry(int begin, int end, PartParser parser) {
			this.begin = begin;
			this.end = end;
			this.parser = parser;
		}

	}

}
